/*
 * Copyright 1999-2011 Alibaba Group.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.dubbo.rpc.cluster.directory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;

import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.registry.CommonConfiguration;
import com.alibaba.dubbo.rpc.Invocation;
import com.alibaba.dubbo.rpc.Invoker;
import com.alibaba.dubbo.rpc.RpcException;
import com.alibaba.dubbo.rpc.cluster.Directory;
import com.alibaba.dubbo.rpc.cluster.Router;
import com.alibaba.dubbo.rpc.cluster.RouterFactory;
import com.alibaba.dubbo.rpc.cluster.router.MockInvokersSelector;
import com.lvmama.pub.DistributedContext;

/**
 * 增加router的Directory
 * 
 * @author chao.liuc
 */
public abstract class AbstractDirectory<T> implements Directory<T> {

    // 日志输出
    private static final Logger logger = LoggerFactory.getLogger(AbstractDirectory.class);

    private final URL url ;
    
    private volatile boolean destroyed = false;

    private volatile URL consumerUrl ;
    
	private volatile List<Router> routers;
    
    public AbstractDirectory(URL url) {
        this(url, null);
    }
    
    public AbstractDirectory(URL url, List<Router> routers) {
    	this(url, url, routers);
    }
    
    public AbstractDirectory(URL url, URL consumerUrl, List<Router> routers) {
        if (url == null)
            throw new IllegalArgumentException("url == null");
        this.url = url;
        this.consumerUrl = consumerUrl;
        setRouters(routers);
    }
    
    public List<Invoker<T>> list(Invocation invocation) throws RpcException {
        if (destroyed){
            throw new RpcException("Directory already destroyed .url: "+ getUrl());
        }
        List<Invoker<T>> invokers = doList(invocation);
        List<Router> localRouters = this.routers; // local reference
        if (localRouters != null && localRouters.size() > 0) {
            for (Router router: localRouters){
                try {
                    if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, true)) {
                        invokers = router.route(invokers, getConsumerUrl(), invocation);
                    }
                } catch (Throwable t) {
                    logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
                }
            }
        }

        //排除下线服务
		try {
			invokers = excludeOfflineInvokers(invokers);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
		
		// 获取服务列表
		try {
			invokers = fliterInvokersByClientIp(invocation,invokers);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
        
        return invokers;
    }
    
    public URL getUrl() {
        return url;
    }
    
    public List<Router> getRouters(){
        return routers;
    }

	public URL getConsumerUrl() {
		return consumerUrl;
	}

	public void setConsumerUrl(URL consumerUrl) {
		this.consumerUrl = consumerUrl;
	}
	
	/**
	 * 排除下线的服务提供者
	 * 
	 * @param invokers
	 * @return
	 */
	public List<Invoker<T>> excludeOfflineInvokers(List<Invoker<T>> invokers) {
		if (invokers == null) {
			return null;
		}

		// 开关状态
		boolean openFlag = CommonConfiguration.getBoolean("SWITCH_OFFLINE_ENABLE");
		if (!openFlag) {
			return invokers;
		}

		List<Invoker<T>> newInvokers = new ArrayList<Invoker<T>>();
		for (Invoker<T> invoker : invokers) {
			String providerIP = invoker.getUrl().getParameter(Constants.PROVIDER_IP);
			String app = invoker.getUrl().getParameter(Constants.PROVIDER_APPLICATION);
			String ipKey = "SWITCH_OFFLINE_" + providerIP;
			String appKey = "SWITCH_OFFLINE_" + providerIP + "@" + app;
			if (!CommonConfiguration.getBoolean(ipKey) && !CommonConfiguration.getBoolean(appKey)) {
				newInvokers.add(invoker);
			}
		}

		return newInvokers;
	}
	
	

	/**
	 * 获取服务列表:先从浏览器ip对应的路由表中获取服务，如果没有则从一台机器ip对应的路由表中获取服务
	 * @param invokers
	 * @return
	 */
	public List<Invoker<T>> fliterInvokersByClientIp(Invocation invocation,List<Invoker<T>> invokers) {

		if (invokers == null) {
			return null;
		}
		
		// 开关状态
		boolean openFlag = CommonConfiguration.getBoolean("SWITCH_PRIVATE_CYCLE");
		if (!openFlag) {
			return invokers;
		}

		// 开关状态
		boolean logFlag = CommonConfiguration.getBoolean("SWITCH_PRIVATE_CYCLE_LOG");
		
		Object broserIPObj = DistributedContext.getContext().get("broserIP");
		Object webIPObj = DistributedContext.getContext().get("webIP");
		
		if (broserIPObj == null && webIPObj == null) {
			//任何ip信息都没有，原始返回
			if (logFlag) {
				logger.info("cycle route not found any source ip for service " + getInterface().getName() + "." + invocation.getMethodName());
			}

			broserIPObj = getConsumerUrl().getHost();
		}
		
		// 浏览器获取路由ip对应的服务
		if (logFlag) {
			logger.info("cycle route try to find from browser ip " + broserIPObj +" for service " + getInterface().getName() + "." + invocation.getMethodName());
		}
		String routerCycles = CommonConfiguration.getValue("ROUTE_CYCLE_" + String.valueOf(broserIPObj)) != null
				? CommonConfiguration.getValue("ROUTE_CYCLE_" + String.valueOf(broserIPObj)) 
				: String.valueOf(broserIPObj);
		List<Invoker<T>> filterInvokers = getInvokersByRouterTable(invokers, routerCycles);

		// not found, find from firstVisit ip
		if (filterInvokers == null || filterInvokers.size() == 0) {
			if (logFlag) {
				logger.info("cycle route try to find from first visit ip " + webIPObj +" for service " + getInterface().getName() + "." + invocation.getMethodName());
			}
			routerCycles = CommonConfiguration.getValue("ROUTE_CYCLE_" + String.valueOf(webIPObj)) != null
					? CommonConfiguration.getValue("ROUTE_CYCLE_" + String.valueOf(webIPObj))
					: String.valueOf(webIPObj);
			filterInvokers = getInvokersByRouterTable(invokers, routerCycles);

			// not found, find from default cycle
			if (filterInvokers == null || filterInvokers.size() == 0) {
				if (logFlag) {
					logger.info("cycle route try to find from default cycle for service " + getInterface().getName() + "." + invocation.getMethodName());
				}
				filterInvokers = getInvokersByRouterTable(invokers,
						CommonConfiguration.getValue("ROUTE_CYCLE_DEFAULT"));
			}
		}
		
		Boolean routeCycleRestrict = CommonConfiguration.getBoolean("SWITCH_ROUTE_CYCLE_RESTRICT_" + String.valueOf(broserIPObj != null ? broserIPObj : webIPObj));
		
		if (routeCycleRestrict && filterInvokers!= null && filterInvokers.size() > 0) {
			// if cycle restrict, double check the invoker
			
			String routerRestrictCycles = CommonConfiguration.getValue("ROUTE_CYCLE_" + String.valueOf(broserIPObj != null ? broserIPObj : webIPObj));
			int i = 0 ;
			Boolean sameApp = false;
			
			for (; i < filterInvokers.size(); i++) {
			// for (Invoker<T> invoker : filterInvokers) {
				Invoker<T> invoker = filterInvokers.get(i);
				
			    String providerIP = invoker.getUrl().getParameter(Constants.PROVIDER_IP);
			    String app = invoker.getUrl().getParameter(Constants.PROVIDER_APPLICATION);
			    
			    if (!sameApp){
			    	if(("" + routerRestrictCycles).indexOf("@" + app) > -1){
			    		sameApp = true;
				    }
			    }
			    
			    if(("" + routerRestrictCycles).indexOf(providerIP + "@" + app) > -1){
			    	break;
			    }
			}
			
			if (i >= filterInvokers.size() && sameApp) {
				if (logFlag) {
					logger.info("cycle restrict, no match invoker found for service " + getInterface().getName() + "." + invocation.getMethodName());
				}
				return null;
			}
		}

		
		if (filterInvokers == null || filterInvokers.size() == 0) {
			// if cycle restrict
			if (routeCycleRestrict) {
				if (logFlag) {
					logger.info("cycle restrict, no invoker found for service " + getInterface().getName() + "." + invocation.getMethodName());
				}
				return filterInvokers;
			}
			
			// return all invokers
			if (logFlag) {
				logger.info("cycle route return all invokers for service " + getInterface().getName() + "." + invocation.getMethodName());
			}
			return invokers;
		}
		
		return filterInvokers;
		
	}
	
	
	/**
	 * 进一步匹配路由相等的服务提供者
	 * 
	 * @param invokers
	 * @param routerCycles
	 * @return
	 */
	public List<Invoker<T>> getInvokersByRouterTable(List<Invoker<T>> invokers, String routerCycles) {
		
		List<Invoker<T>> filterInvokers = new ArrayList<Invoker<T>>();

		if (StringUtils.isEmpty(routerCycles)) {
			return null;
		}

		String[] routerIPs = routerCycles.split(",");
		Map<String, String> ipApps = new HashMap<String, String>();
		List<String> ips = new ArrayList<String>();
		for (String routerIP : routerIPs) {
		    String[] ipApp = routerIP.split("@");
		    if (ipApp.length > 1) {
		        if (ipApps.containsKey(ipApp[0])) {
		            ipApps.put(ipApp[0], ipApps.get(ipApp[0]) + "#" + ipApp[1].toLowerCase());
		        } else {
		            ipApps.put(ipApp[0].trim(), ipApp[1].toLowerCase());
		        }
		    } else {
		        ips.add(ipApp[0].trim());
		    }
		}

		for (Invoker<T> invoker : invokers) {
		    String providerIP = invoker.getUrl().getParameter(Constants.PROVIDER_IP);
		    String app = invoker.getUrl().getParameter(Constants.PROVIDER_APPLICATION);

		    if (StringUtils.isEmpty(providerIP)) continue;

		    if (ips.contains(providerIP)) {    // IP 匹配，默认IP下所有的服务都符合条件
		        filterInvokers.add(invoker);
		    } else if (ipApps.containsKey(providerIP) && ipApps.get(providerIP).indexOf(app) >= 0) {    // IP_APP匹配
		        filterInvokers.add(invoker);
		    }
		}

//		for (Invoker<T> invoker : invokers) {
//			String providerIP = invoker.getUrl().getHost();
//			invoker.getUrl().getParameter(Constants.APPLICATION_KEY);
//			for (String routerIP : routerIPs) {
//				// 服务提供ip与目标ip匹配了，则需要该服务提供者
//				if (providerIP != null && providerIP.equals(routerIP)) {
//					filterInvokers.add(invoker);
//					// break;
//				}
//			}
//		}
		
		return filterInvokers;
	}
	
	
	
	
	
    protected void setRouters(List<Router> routers){
        // copy list
        routers = routers == null ? new  ArrayList<Router>() : new ArrayList<Router>(routers);
        // append url router
    	String routerkey = url.getParameter(Constants.ROUTER_KEY);
        if (routerkey != null && routerkey.length() > 0) {
            RouterFactory routerFactory = ExtensionLoader.getExtensionLoader(RouterFactory.class).getExtension(routerkey);
            routers.add(routerFactory.getRouter(url));
        }
        // append mock invoker selector
        routers.add(new MockInvokersSelector());
        Collections.sort(routers);
    	this.routers = routers;
    }

    public boolean isDestroyed() {
        return destroyed;
    }

    public void destroy(){
        destroyed = true;
    }

    protected abstract List<Invoker<T>> doList(Invocation invocation) throws RpcException ;

}